#pragma once
#include "socket.h"
#include "task.h"
#include <cerrno>
#include <chrono>
#include <coroutine>
#include <cstddef>
#include <fcntl.h>
#include <functional>
#include <map>
#include <queue>
#include <span>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unordered_set>
#include <utility>

namespace cobt {

constexpr int COBT_EPOLL_LOOP_MAX_EVENTS = 8192;
constexpr size_t COBT_MAX_COROUTINES = 128;

class EpollLoop {
  friend class EventAwaiter;

public:
  EpollLoop();
  ~EpollLoop();
  EpollLoop(const EpollLoop &) = delete;
  EpollLoop &operator=(const EpollLoop &) = delete;
  EpollLoop(EpollLoop &&);
  EpollLoop &operator=(EpollLoop &&);
  void add_event(epoll_event *, int);
  void remove_event(int);
  void modify_event(epoll_event *, int);
  void run();
  void enqueue(std::coroutine_handle<> coroutine);
  bool empty() const noexcept;
  void add_timer(std::chrono::steady_clock::time_point time,
                 std::function<void()> func);
  size_t get_queue_size() const noexcept;
  std::chrono::steady_clock::duration get_next_timer() const;

private:
  int m_epfd;
  int m_max_events;
  std::queue<std::coroutine_handle<>> m_coroutines;
  std::multimap<std::chrono::steady_clock::time_point, std::function<void()>>
      m_timer;
  epoll_event m_events[COBT_EPOLL_LOOP_MAX_EVENTS];
};

inline thread_local EpollLoop g_loop;

class EventAwaiter {
public:
  friend class EpollLoop;
  friend Task<int> read_async(int fd, std::span<char> buf,
                              std::chrono::milliseconds timeout);
  friend Task<int> write_async(int fd, std::span<char> buf,
                               std::chrono::milliseconds timeout);
  explicit EventAwaiter(EpollLoop &loop, int fd, uint32_t events, int id = 0);
  bool await_ready() const noexcept;
  void await_suspend(std::coroutine_handle<> handle);
  void await_resume() const noexcept;
  ~EventAwaiter();

private:
  EpollLoop &m_loop;
  epoll_event m_event;
  std::coroutine_handle<> m_pre;
  int m_fd;
  int m_id;
};

inline std::unordered_set<EventAwaiter *> g_awaiter_set;


Task<int> read_async(int fd, std::span<char> buf,
                     std::chrono::milliseconds timeout);
Task<int> write_async(int fd, std::span<char> buf,
                      std::chrono::milliseconds timeout);
Task<int> accept_async(int fd);

Task<int> connect_async(int fd, const SocketAddress &addr,
                        std::chrono::milliseconds timeout);

// template <class A>
//   requires(Awaitable<A>)
// inline void loop_enqueue(A &&task) {
//   auto coroutine =
//       task.operator co_await().await_suspend(std::noop_coroutine());
//   g_loop.enqueue(coroutine);
//   task.m_handle = nullptr;
// }

template <class A, class... Args>
  requires(Awaitable<A>)
inline void loop_enqueue(std::function<A(Args...)> task, Args... args) {
    auto t = std::move(task)(args...);
    auto coroutine = t.operator co_await().await_suspend(std::noop_coroutine());
    g_loop.enqueue(coroutine);
    t.m_handle = nullptr;
    return;
}

template <class T, class A, class... Args>
  requires Awaitable<A>
inline void loop_enqueue(A (T::*task)(Args...), T *obj, Args... args) {
  auto bound_task = [task = std::move(task), obj](Args... args) -> A {
    return std::bind(task, obj, args...)();
  };
  loop_enqueue(std::function(bound_task), args...);
}

void set_timeout(std::chrono::milliseconds timeout, std::function<void()>);
void set_timeout(std::chrono::steady_clock::time_point expire_time,
                 std::function<void()> cb);

} // namespace cobt